package com.yuandian.ops.workbench.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.yuandian.api.management.entity.ConfReturnCode;
import com.yuandian.api.management.feign.RemoteAreaService;
import com.yuandian.api.management.feign.RemoteBusinessService;
import com.yuandian.api.management.feign.RemoteReturnCodeService;
import com.yuandian.api.management.feign.RemoteStepService;
import com.yuandian.bpm.admin.api.entity.SysDictItem;
import com.yuandian.bpm.admin.api.feign.RemoteDictService;
import com.yuandian.bpm.common.core.constant.SecurityConstants;
import com.yuandian.bpm.common.core.util.R;
import com.yuandian.constant.DictType;
import com.yuandian.ops.workbench.mapper.AlarmDetailAnalysisMapper;
import com.yuandian.ops.workbench.po.AlarmDetailParams;
import com.yuandian.ops.workbench.service.IAlarmDetailAnalysisService;
import com.yuandian.ops.workbench.utils.DataTypeUtil;
import com.yuandian.utils.PartitionUtil;
import com.yuandian.ops.workbench.utils.TaskProcessUtil;
import com.yuandian.ops.workbench.vo.*;
import com.yuandian.utils.BeanUtils;
import com.yuandian.utils.PageDataInfo;
import com.yuandian.utils.YDateUtil;
import com.yuandian.utils.poi.ExcelTitleUtils;
import com.yuandian.utils.poi.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * BPM 4.0
 * 运维工作台 - 告警详情分析
 */
@Slf4j
@Service
public class AlarmDetailAnalysisServiceImpl implements IAlarmDetailAnalysisService {

    @Autowired
    private AlarmDetailAnalysisMapper alarmDetailAnalysisMapper;

	@Autowired
	private RemoteStepService remoteStepService;

	@Autowired
	private RemoteDictService remoteDictService;

	@Autowired
	private RemoteReturnCodeService remoteReturnCodeService;

	@Autowired
	private RemoteAreaService remoteAreaService;

	@Autowired
	private RemoteBusinessService remoteBusinessService;





	/** 指定线程池的线程数(此处核心线程数和最大线程数一致) */
    private static final int THREAD_POOL_SIZE = 6;

    /** 指定每批次查询大小 */
    private static final int QUERY_BATCH_SIZE = 150;


    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-顶部折线图数据
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public AlarmDetailVo getAlarmDetailTrend(AlarmDetailParams params) {
        AlarmDetailVo result = new AlarmDetailVo();
        result.setStartTime(params.getStartTime());
        result.setEndTime(params.getEndTime());
        List<AlarmDetailTrendVo> detailListVoList = new ArrayList<>();

        // 1. 重置 ‘startTime’ 到当天的零点
        String startTime = YDateUtil.yyyy_MM_dd_000000(params.getStartTime());
        params.setStartTime(startTime);

        // 2.获取第二天零点
        String endTime = YDateUtil.getAfterDay(startTime);
        params.setEndTime(endTime);

        // 3.查询数据, 并进行拼装(一分钟一个刻度)
        List<AlarmDetailTrendVo> list = alarmDetailAnalysisMapper.getAlarmDetailTrend(params);
        if (!CollectionUtils.isEmpty(list)) {
            Map<String, AlarmDetailTrendVo> map = new HashMap<>();
            map = list.stream().collect(Collectors.toMap(AlarmDetailTrendVo::getCycleTime, v -> v, (k1, k2) -> k1));
            long start = Objects.requireNonNull(YDateUtil.parse(params.getStartTime())).getTime();
            long end = Objects.requireNonNull(YDateUtil.parse(params.getEndTime())).getTime();

            while (start < end) {
                String cycleTime = DateUtil.formatDateTime(new Date(start));
                AlarmDetailTrendVo alarmDetailTrendVo = map.get(cycleTime);
                if (alarmDetailTrendVo == null)  {
                    alarmDetailTrendVo = new AlarmDetailTrendVo();
                    alarmDetailTrendVo.setHealthRate(0.0f);
                    alarmDetailTrendVo.setDealTimeAlarmRate(0.0f);
                    alarmDetailTrendVo.setFailRate(0.0f);
                    alarmDetailTrendVo.setCycleTime(cycleTime);
                }
                detailListVoList.add(alarmDetailTrendVo);
                start = start + 60L * 1000;
            }
        }
        result.setDetailList(detailListVoList);

        // 3.根据时间档案ID获取对应的告警事件列表
        List<String> groupCycleTimeList = alarmDetailAnalysisMapper.getGroupCycleTimeList(params);
        result.setGroupCycleTimeList(groupCycleTimeList);

        // 4.返回结果集
        return result;
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-告警总览
     * @return 结果集
     */
    @Override
    public AlarmDetailOverviewVo getAlarmDetailOverview(AlarmDetailParams params) {
        AlarmDetailOverviewVo vo = new AlarmDetailOverviewVo();
        List<String> partitionList = new ArrayList<>();

        // 根据groupId查询对应告警数据的时间周期
        String partition = PartitionUtil.getPartitionBasedOnTime(params.getStartTime());
        if (StringUtils.isNotEmpty(partition)) {
            partitionList.add(partition);
            params.setPartitionList(partitionList);
        }

        // 查询数据
        if (StringUtils.isNotEmpty(params.getGroupId())) {
            // 如果是事件档案分析则根据 groupId 查 dws_business_observe_alarm表
            vo = alarmDetailAnalysisMapper.getAlarmDetailOverview(params);
            if (vo == null) { return null; }
            // 查询该业务或步骤接口其他的事件事件档案时间
            List<ObserveAlarmVo> otherAlarmTimeList = alarmDetailAnalysisMapper.getOtherAlarmTimeList(params);
            vo.setOtherAlarmTimeList(otherAlarmTimeList);

        } else {
            // 如果是'单个周期数据'为粒度的告警分析则需要根据Id查 dws_business_status_alarm 表
            vo = alarmDetailAnalysisMapper.getAlarmDetailOverviewById(params);
			if (vo == null) { return new AlarmDetailOverviewVo(); }

            // 组装查询条件，下面查对应告警次数
            params.setSystemId(vo.getSystemId());
            params.setBusinessCode(vo.getBusinessCode());
            params.setAlarmIndicatorCode(vo.getAlarmIndicatorCode());
            params.setDataType(vo.getDataType());
            // 重置 ‘startTime’ 到当天的零点以及获取第二天零点
            String startTime = YDateUtil.yyyy_MM_dd_000000(params.getStartTime());
            String endTime = YDateUtil.getAfterDay(startTime);
            params.setStartTime(startTime);
            params.setEndTime(endTime);
        }

        // 查询对应告警次数
        int alarmNum = alarmDetailAnalysisMapper.getAlarmNumByGroupId(params);
        vo.setAlarmNum(alarmNum);

        // 补充 ’%‘
        vo.setHealthRate(vo.getHealthRate() + "%");
        vo.setFailRate(vo.getFailRate() + "%");
        vo.setDealTimeAlarmRate(vo.getDealTimeAlarmRate() + "%");

        return vo;
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-告警详情步骤分析
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public PageDataInfo getStepAnalysis(AlarmDetailParams params) {
        Page<StepAnalysisVo> page = new Page<>(params.getPageNum(), params.getPageSize());
        List<String> groupCycleTimeList = new ArrayList<>();
		List<String> partitionList = new ArrayList<>();

		// 根据groupId查询对应告警数据的时间周期
		String partition = PartitionUtil.getPartitionBasedOnTime(params.getStartTime());
		if (StringUtils.isNotEmpty(partition)) {
			partitionList.add(partition);
			params.setPartitionList(partitionList);
		}

        if (StringUtils.isNotEmpty(params.getGroupId())) {
            // 根据groupId查询对应告警数据的时间周期
            groupCycleTimeList = alarmDetailAnalysisMapper.getGroupCycleTimeList(params);

        } else {
            // 单个周期的告警分析用以下条件
            groupCycleTimeList = Collections.singletonList(params.getStartTime());
        }

        params.setGroupCycleTimeList(groupCycleTimeList);

        // 查询数据
        List<StepAnalysisVo> list = alarmDetailAnalysisMapper.getStepAnalysis(page, params);

        // 查询并回填对应步骤类型
        if (!CollectionUtils.isEmpty(list)) {
            List<String> stepCodeList = list.stream().map(StepAnalysisVo::getStepCode).collect(Collectors.toList());
            Map<String, String> stepTypeMap = getStepTypeByStepCode(stepCodeList);
            for (StepAnalysisVo vo : list) {
                String key = vo.getBusinessCode() + "@" + vo.getStepCode();
                vo.setStepType(stepTypeMap.get(key));
                vo.setGroupId(params.getGroupId());
            }
        }

        return new PageDataInfo(page, list);
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-告警详情步骤分析Excel
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public void getStepAnalysisExcel(AlarmDetailParams params, HttpServletResponse response) {
        try {
            // 1、查询要导出的数据
            params.setPageNum(1);
            params.setPageSize(-1);
            List<StepAnalysisVo> list = BeanUtils.cast(getStepAnalysis(params).getData());

            Map<String, String> stepTypeConvert = new HashMap<>(4);
            stepTypeConvert.put("1","首");
            stepTypeConvert.put("2","中间");
            stepTypeConvert.put("3","末");
            for (StepAnalysisVo s : list) {
                s.setStepType(stepTypeConvert.getOrDefault(s.getStepType(),"未知步骤"));
                s.setFailRate(s.getFailRate() + "%");
                s.setDealTimeAlarmCountRate(s.getDealTimeAlarmCountRate() + "%");
                s.setHealthRate(s.getHealthRate() + "%");
            }
            // 2、导出Excel
            //设置表头
            List<String> headParamsList = new ArrayList<>();
            String systemName = getSystemName(params.getSystemName());
            String businessName = getBusinessName(params.getBusinessName());
            String alarmIndicatorName = getAlarmIndicatorNameByCode(params.getAlarmIndicatorCode());
            String title = "步骤分析数据总览表";
            headParamsList.add(title);
            headParamsList.add(systemName);
            headParamsList.add(businessName);
            headParamsList.add(alarmIndicatorName + "分析");
            String fileName = title + System.currentTimeMillis();
            String formTitleName = ExcelTitleUtils.formTitleName(headParamsList, params.getStartTime());
            List<String> titleList;
            List<String> exclludeColumnFields = null;
            List<List<String>>  headList = new ArrayList<>();

            // 业务告警的指标枚举
//                BS_FAIL_RATE_10  业务失败率
//                BS_FAIL_RATE_20  业务处理时长告警率
//                BS_FAIL_RATE_30  业务健康度

            if("BS_FAIL_RATE_10".equals(params.getAlarmIndicatorCode())) {
                titleList = Arrays.asList("步骤名称", "步骤类型", "请求总量", "响应慢请求量", "失败请求量","告警请求量","失败率");
                exclludeColumnFields = Arrays.asList("dealTimeAlarmCountRate", "healthRate");
                getHeadList(titleList,headList,formTitleName);

            }

            if("BS_FAIL_RATE_20".equals(params.getAlarmIndicatorCode())) {
                titleList = Arrays.asList("步骤名称", "步骤类型", "请求总量", "响应慢请求量", "失败请求量", "告警请求量","响应慢比例");
                exclludeColumnFields = Arrays.asList("failRate", "healthRate");
                getHeadList(titleList,headList,formTitleName);

            }

            if("BS_FAIL_RATE_30".equals(params.getAlarmIndicatorCode())){
                titleList = Arrays.asList("步骤名称", "步骤类型", "请求总量", "响应慢请求量", "失败请求量","告警请求量","健康度");
                exclludeColumnFields = Arrays.asList("dealTimeAlarmCountRate", "failRate");
                getHeadList(titleList,headList,formTitleName);

            }

            ExcelUtil.writeExcel(StepAnalysisVo.class, fileName, title, list, headList, exclludeColumnFields, response);
        } catch (Exception e) {
            e.printStackTrace();
	    }
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-告警详情步骤分析-下钻告警详情列表
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public PageDataInfo getStepAnalysisAlarmDetail(AlarmDetailParams params) {
        Page<StepAlarmListVo> page = new Page<>(params.getPageNum(), params.getPageSize());
        List<String> groupCycleTimeList = new ArrayList<>();
        List<String> partitionList = new ArrayList<>();
        List<String> relvanceDataIdList = new ArrayList<>();

        // 根据groupId查询对应告警数据的时间周期
        String partition = PartitionUtil.getPartitionBasedOnTime(params.getStartTime());
        if (StringUtils.isNotEmpty(partition)) {
            partitionList.add(partition);
            params.setPartitionList(partitionList);
        }

        if (StringUtils.isNotEmpty(params.getGroupId())) {
            groupCycleTimeList = alarmDetailAnalysisMapper.getGroupCycleTimeList(params);
        } else {
            // 单个周期的告警分析用以下条件
            groupCycleTimeList = Collections.singletonList(params.getStartTime());
        }
        params.setGroupCycleTimeList(groupCycleTimeList);

        // 获取步骤ID列表
        params.setIsAlarm(1);

        if (DataTypeUtil.isInterface(params.getDataType())) {
            // 如果是共用接口类型，则需要获取web表数据
            relvanceDataIdList = alarmDetailAnalysisMapper.getRelvanceDataIdList(params);

        } else {
            // 如果是业务或者共用步骤则需要获取step表
            relvanceDataIdList = alarmDetailAnalysisMapper.getStepIdList(params);
        }

        if (relvanceDataIdList == null || relvanceDataIdList.size() == 0) {
            return new PageDataInfo(page, null);
        }

        // 获取告警数据(单线程)
        long startTime = System.currentTimeMillis();

        List<StepAlarmListVo> alarmList = new ArrayList<>();

       /*
        params.setStepIdList(relvanceDataIdList);
        alarmList = alarmDetailAnalysisMapper.getStepAnalysisAlarmDetail(null, params, null);
        log.info("运维工作台-告警详情-查询数据总耗时：{} ms", System.currentTimeMillis() - startTime);
        */

        // 获取告警数据(多线程)  拆分查询条件：detailIdList, 按批次滚动查询数据避免一次性查导致超时问题
        List<List<String>> list = Lists.partition(relvanceDataIdList, QUERY_BATCH_SIZE);

        startTime = System.currentTimeMillis();
        String taskName = "TASK-ALARM-DETAIL";
        try {
            // Create a thread pool to execute the queries in parallel
            ExecutorService executor = TaskProcessUtil.getOrInitExecutors(taskName, THREAD_POOL_SIZE);
            final CountDownLatch latch = new CountDownLatch(list.size());
            List<Future<List<StepAlarmListVo>>> futures = new ArrayList<>();

            for (List<String> idList : list) {
                Callable<List<StepAlarmListVo>> task = new SqlInQueryTaskAlarmDetail(params, latch, idList);
                Future<List<StepAlarmListVo>> future = executor.submit(task);
                futures.add(future);
            }

            for (Future<List<StepAlarmListVo>> future : futures) { alarmList.addAll(future.get()); }
            log.info("运维工作台-告警详情-查询数据总耗时：{} ms", System.currentTimeMillis() - startTime);

        } catch (Exception e) {
            log.error("运维工作台-告警分析-告警详情查询报错", e);
        } finally {
            // Shut down the thread pool
            TaskProcessUtil.releaseExecutors(taskName);
        }

        // 如果是列表查询则需要手动分页返回到前端，下载则不用分页
        if (params.getPageSize() != -1) {
            page.setTotal(alarmList.size());
            alarmList = getAlarmDetailResultListByPage(params, alarmList);
        }

        // 如果是下载Excel则需要回填 '序号'
        if (params.getPageSize() == -1 && !CollectionUtils.isEmpty(alarmList)) {
            int num = 1;
            for (StepAlarmListVo vo : alarmList) {
                vo.setNum(num++);
            }
        }

        return new PageDataInfo(page, alarmList);
    }

    private class SqlInQueryTaskAlarmDetail implements Callable<List<StepAlarmListVo>> {

        private final AlarmDetailParams params;

        private final CountDownLatch latch;

        private final List<String> detailIdList;

        public SqlInQueryTaskAlarmDetail(AlarmDetailParams params, CountDownLatch latch, List<String> detailIdList) {
            this.params = params;
            this.latch = latch;
            this.detailIdList = detailIdList;
        }

        @Override
        public List<StepAlarmListVo> call() {
            List<StepAlarmListVo> alarmList = new ArrayList<>();
            try {
                alarmList = alarmDetailAnalysisMapper.getStepAnalysisAlarmDetail(null, params, detailIdList);

            } catch (Exception e) {
                log.error("运维工作台-告警分析-影响用户查询报错", e);

            } finally {
                if (latch != null) { latch.countDown();  }
            }
            return alarmList;
        }
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-告警详情步骤分析-下钻告警详情列表
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public void getStepAnalysisAlarmDetailExcel(AlarmDetailParams params, HttpServletResponse response) throws ParseException, IOException {

        // 1、查询要导出的数据
        params.setPageNum(1);
        params.setPageSize(-1);
        List<StepAlarmListVo> list = BeanUtils.cast(getStepAnalysisAlarmDetail(params).getData());

        List<String> headParamsList = new ArrayList<>();
        String systemName = getSystemName(params.getSystemName());
        String businessName = getBusinessName(params.getBusinessName());
        String alarmIndicatorName = getAlarmIndicatorNameByCode(params.getAlarmIndicatorCode());
        String title = "";
        if (StringUtils.isEmpty(params.getAlarmResultCode())){
            title = "步骤告警明细数据总览表";
            headParamsList.add(title);
            headParamsList.add(systemName);
            headParamsList.add(businessName);
            headParamsList.add(alarmIndicatorName + "告警分析");

        }else {
            title = "告警实值（" + params.getAlarmResultCode() + ")" + "明细数据总览表";
            headParamsList.add(title);
            headParamsList.add(systemName);
            headParamsList.add(businessName);
            headParamsList.add(alarmIndicatorName);
        }

        String fileName = title + System.currentTimeMillis();
        String formTitleName = ExcelTitleUtils.formTitleName(headParamsList, params.getStartTime());
        List<List<String>> headList = new ArrayList<>();
        List<String> titleList = Arrays.asList("序号","所属步骤", "告警类型", "告警级别", "告警时间",
                "所属网段", "请求ip","响应ip","告警对象","告警现象","专属指标");
        titleList.forEach(s -> {
            List<String> childHeadList = new ArrayList<>();
            childHeadList.add(formTitleName);
            childHeadList.add(s);
            headList.add(childHeadList);
        });

        params.setStepIdList(new ArrayList<>());
        ExcelUtil.writeExcel(StepAlarmListVo.class, fileName, title, list, headList,20, response);
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-告警分类统计
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public PageDataInfo getAlarmClassificationStatistics(AlarmDetailParams params) {
        long startTime = System.currentTimeMillis();
        List<String> groupCycleTimeList = new ArrayList<>();
        List<String> relvanceDataIdList = new ArrayList<>();
        List<String> partitionList = new ArrayList<>();
        // 根据groupId查询对应告警数据的时间周期
        String partition = PartitionUtil.getPartitionBasedOnTime(params.getStartTime());
        if (StringUtils.isNotEmpty(partition)) {
            partitionList.add(partition);
            params.setPartitionList(partitionList);
        }

        if (StringUtils.isNotEmpty(params.getGroupId())) {
            groupCycleTimeList = alarmDetailAnalysisMapper.getGroupCycleTimeList(params);
        } else {
            // 单个周期的告警分析用以下条件
            groupCycleTimeList = Collections.singletonList(params.getStartTime());
        }

        params.setGroupCycleTimeList(groupCycleTimeList);


        // 获取步骤ID列表
        params.setIsAlarm(1);
        startTime = System.currentTimeMillis();
        if (DataTypeUtil.isInterface(params.getDataType())) {
            // 如果是共用接口类型，则需要获取web表数据
            relvanceDataIdList = alarmDetailAnalysisMapper.getRelvanceDataIdList(params);

        } else {
            // 如果是业务或者共用步骤则需要获取step表
            relvanceDataIdList = alarmDetailAnalysisMapper.getStepIdList(params);
        }
        log.info("运维工作台-告警分类查询接口-获取告警ID列表耗时：{} ms", System.currentTimeMillis() - startTime);


        if (relvanceDataIdList == null || relvanceDataIdList.size() == 0) {
            return new PageDataInfo(0, new ArrayList<>());
        }

        List<StepAlarmListVo> alarmList = new ArrayList<>();
/*

        // 获取告警数据 （单线程）
        params.setStepIdList(relvanceDataIdList);
        startTime = System.currentTimeMillis();
        alarmList = alarmDetailAnalysisMapper.getStepAnalysisAlarmDetail(null, params);
        log.info("运维工作台-告警分类查询接口-查询业务数据耗时：{} ms", System.currentTimeMillis() - startTime);
*/

        // 获取告警数据 （多线程）
        List<List<String>> list = Lists.partition(relvanceDataIdList, QUERY_BATCH_SIZE);
        startTime = System.currentTimeMillis();
        String taskName = "TASK-ALARM-CLASSIFY";
        try {
            // Create a thread pool to execute the queries in parallel
            ExecutorService executor = TaskProcessUtil.getOrInitExecutors(taskName, THREAD_POOL_SIZE);
            final CountDownLatch latch = new CountDownLatch(list.size());
            List<Future<List<StepAlarmListVo>>> futures = new ArrayList<>();

            for (List<String> relvanceDataId : list) {
                Callable<List<StepAlarmListVo>> task = new SqlInQueryAlarmTask(params, latch, relvanceDataId);
                Future<List<StepAlarmListVo>> future = executor.submit(task);
                futures.add(future);
            }

            for (Future<List<StepAlarmListVo>> future : futures) { alarmList.addAll(future.get()); }
            log.info("运维工作台-告警分类查询接口-查询业务数据耗时：{} ms", System.currentTimeMillis() - startTime);

        } catch (Exception e) {
            log.error("运维工作台-告警分析-告警分类查询接口查询报错", e);
        } finally {
            // Shut down the thread pool
            TaskProcessUtil.releaseExecutors(taskName);
        }


        // 根据 alarmResultCode + alarmIndicatorId 对 'alarmList' 进行分组
        HashMap<String, AlarmClassifyVo> resultMap = new HashMap<>();

        alarmList.forEach(stepAlarmListVo -> {
            String key = stepAlarmListVo.getAlarmResultCode() + "@" + stepAlarmListVo.getAlarmIndicatorId();
            AlarmClassifyVo vo = resultMap.get(key);

            if (vo == null) {
                vo = new AlarmClassifyVo();
                vo.setAlarmResultCode(stepAlarmListVo.getAlarmResultCode());
                vo.setAlarmTypeId(String.valueOf(stepAlarmListVo.getAlarmTypeId()));
                vo.setAlarmIndicatorId(stepAlarmListVo.getAlarmIndicatorId());
                vo.setAlarmTypeName(stepAlarmListVo.getAlarmTypeName());
                vo.setAlarmNum(1);
                vo.setLastAlarmTime(stepAlarmListVo.getAlarmTriggerTime());
                vo.setFirstAlarmTime(stepAlarmListVo.getAlarmTriggerTime());
                resultMap.put(key,vo);

            } else {
                vo.setAlarmNum(vo.getAlarmNum() + 1);
                if (YDateUtil.compare(vo.getFirstAlarmTime(), stepAlarmListVo.getAlarmTriggerTime()) > 0) {
                    vo.setFirstAlarmTime(stepAlarmListVo.getAlarmTriggerTime());
                }
                if(YDateUtil.compare(stepAlarmListVo.getAlarmTriggerTime(), vo.getLastAlarmTime()) > 0) {
                    vo.setLastAlarmTime(stepAlarmListVo.getAlarmTriggerTime());
                }
            }
        });

        List<AlarmClassifyVo> resultList = new ArrayList<>();
        for(Map.Entry<String, AlarmClassifyVo> entry : resultMap.entrySet()) {
            AlarmClassifyVo vo = entry.getValue();
            vo.setGroupId(params.getGroupId());
            resultList.add(vo);
        }

        PageDataInfo pageDataInfo = new PageDataInfo(0, new ArrayList<>());
        if (resultList.size() > 0) {
            List<AlarmClassifyVo> listPage = getListPage((int) params.getPageNum(), (int) params.getPageSize(), resultList);
            pageDataInfo = new PageDataInfo(resultList.size(), listPage);
        }

        return pageDataInfo;
    }

    private class SqlInQueryAlarmTask implements Callable<List<StepAlarmListVo>> {

        private final AlarmDetailParams params;

        private final CountDownLatch latch;

        private final List<String> relvanceDataId;

        public SqlInQueryAlarmTask(AlarmDetailParams params, CountDownLatch latch, List<String> relvanceDataId) {
            this.params = params;
            this.latch = latch;
            this.relvanceDataId = relvanceDataId;
        }

        @Override
        public List<StepAlarmListVo> call() {
            List<StepAlarmListVo> affectUserList = new ArrayList<>();
            try {
                affectUserList = alarmDetailAnalysisMapper.getStepAnalysisAlarmClassifyDetail(params, relvanceDataId);
            } catch (Exception e) {
                log.error("运维工作台-告警分析-影响用户查询报错", e);

            } finally {
                if (latch != null) { latch.countDown();  }
            }
            return affectUserList;
        }
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-告警分类统计-下载
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public void getAlarmClassificationStatisticsExcel(AlarmDetailParams params, HttpServletResponse response) {
        String titleName = getAlarmTypeName(params.getAlarmIndicatorIdList());
        try {
            params.setPageNum(1);
            // 由于mybatis-plus 分页查询限制 500 条,小于0 如 -1 不受限制
            params.setPageSize(-1);
            List<AlarmClassifyVo> resultList = BeanUtils.cast(getAlarmClassificationStatistics(params).getData());

            // 设置表头
            List<String> headParamsList = new ArrayList<>();
            String systemName = getSystemName(params.getSystemName());
            String businessName = getBusinessName(params.getBusinessName());
            String alarmIndicatorName = getAlarmIndicatorNameByCode(params.getAlarmIndicatorCode());
            String title =  "告警分类(" + titleName + ")统计数据总览表";
            headParamsList.add(title);
            headParamsList.add(systemName);
            headParamsList.add(businessName);
            headParamsList.add(alarmIndicatorName + "分析");
            String fileName = title + System.currentTimeMillis();
            String formTitleName = ExcelTitleUtils.formTitleName(headParamsList, params.getStartTime());
            List<List<String>> headList = new ArrayList<>();
            List<String> titleList = Arrays.asList("告警实值", "告警类型", "告警数", "最早告警时间", "最新告警时间");
            titleList.forEach(s -> {
                List<String> childHeadList = new ArrayList<>();
                childHeadList.add(formTitleName);
                childHeadList.add(s);
                headList.add(childHeadList);
            });

            ExcelUtil.writeExcel(AlarmClassifyVo.class, fileName, title, resultList, headList, 25, response);

        } catch (Exception e) {
            e.printStackTrace();
		}
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-影响用户统计
     * @param params 请求参数
     * @c
     */
    @Override
    public PageDataInfo getAffectUser(AlarmDetailParams params) {
        long startTime = System.currentTimeMillis();
        // 0.初始化各集合
        Page<AffectUserVo> page = new Page<>(params.getPageNum(), params.getPageSize());
        List<AffectUserVo> userList = new ArrayList<>();
        List<AffectUserVo> resultList = new ArrayList<>();
        List<String> partitionList = new ArrayList<>();

        // 1.根据startTime计算分区
        String partition = PartitionUtil.getPartitionBasedOnTime(params.getStartTime());
        if (StringUtils.isNotEmpty(partition)) {
            partitionList.add(partition);
            params.setPartitionList(partitionList);
        }

        // 2.通过 groupId 判断是查询 ’事件档案‘ 类型的数据，还是 ’单个告警周期‘ 的数据
        List<String> groupCycleTimeList = getGroupCycleTimeList(params);
        params.setGroupCycleTimeList(groupCycleTimeList);
        params.setIsAlarm(1);

        // 3. 获取详单ID列表
        List<String> detailIdList = getDetailIdList(params);

        // 4.详单ID列表为空则返回空
        if (CollectionUtils.isEmpty(detailIdList)) { return new PageDataInfo(0,new ArrayList<>()); }

        // 5.拆分查询条件：detailIdList, 按批次滚动查询数据避免一次性查导致超时问题
        List<List<String>> list = Lists.partition(detailIdList, QUERY_BATCH_SIZE);

        /*
        startTime = System.currentTimeMillis();
        for (List<String> idList : list) {
            userList.addAll(alarmDetailAnalysisMapper.getAffectUser(params, idList));
        }
        log.info("运维工作台-影响用户接口-查询数据总耗时：{} ms", System.currentTimeMillis() - startTime);
*/
        startTime = System.currentTimeMillis();
        String taskName = "TASK-AFFECT-USER";
        try {
            // Create a thread pool to execute the queries in parallel
            ExecutorService executor = TaskProcessUtil.getOrInitExecutors(taskName, THREAD_POOL_SIZE);
            final CountDownLatch latch = new CountDownLatch(list.size());
            List<Future<List<AffectUserVo>>> futures = new ArrayList<>();
            for (List<String> idList : list) {
                Callable<List<AffectUserVo>> task = new SqlInQueryTask(params, latch, idList);
                Future<List<AffectUserVo>> future = executor.submit(task);
                futures.add(future);
            }

            for (Future<List<AffectUserVo>> future : futures) { userList.addAll(future.get()); }
            log.info("运维工作台-影响用户接口-查询数据总耗时：{} ms", System.currentTimeMillis() - startTime);

        } catch (Exception e) {
            log.error("运维工作台-告警分析-影响用户查询报错", e);
        } finally {
            // Shut down the thread pool
            TaskProcessUtil.releaseExecutors(taskName);
        }

        if (detailIdList.size() > userList.size()) {
            Map<String, AffectUserVo> map = userList .stream()
                    .collect(Collectors.toMap(AffectUserVo::getDetailId, v -> v, (k1, k2) -> k1));
            for (String detailId : detailIdList) {
                AffectUserVo affectUserVo = map.get(detailId);
                if (affectUserVo != null) { resultList.add(affectUserVo); }
            }
        } else {
            resultList = userList;
        }

        // 6.如果是列表查询则需要手动分页返回到前端，下载则不用分页
        if (params.getPageSize() != -1) {
            page.setTotal(resultList.size());
            resultList = getResultListByPage(params, resultList);
        }

        // 7.回填 自定义返回码、地区名称、计算传输耗时等指标
        conductIndexBackfill(resultList);

        // 8.返回结果集
        return new PageDataInfo(page, resultList);
    }

    private class SqlInQueryTask implements Callable<List<AffectUserVo>> {

        private final AlarmDetailParams params;

        private final CountDownLatch latch;

        private final List<String> detailIdList;

        public SqlInQueryTask(AlarmDetailParams params, CountDownLatch latch, List<String> detailIdList) {
            this.params = params;
            this.latch = latch;
            this.detailIdList = detailIdList;
        }

        @Override
        public List<AffectUserVo> call() {
            List<AffectUserVo> affectUserList = new ArrayList<>();
            try {
                affectUserList = alarmDetailAnalysisMapper.getAffectUser(params, detailIdList);

            } catch (Exception e) {
                log.error("运维工作台-告警分析-影响用户查询报错", e);

            } finally {
                if (latch != null) { latch.countDown();  }
            }
            return affectUserList;
        }
    }

    /**
     * 回填 自定义返回码、地区名称、计算传输耗时等指标
     * @param resultList
     */
    private void conductIndexBackfill(List<AffectUserVo> resultList) {
        if (!CollectionUtils.isEmpty(resultList)) {
            // 获取 自定义返回码注释 回填到前端
            Map<String, String> codeMap = getCodeExplain();
            String explain = "";
            // 获取地区名称
            Map<String, String> areaMap = getAreaName();
            for (AffectUserVo vo : resultList) {
                explain = codeMap.get(vo.getSystemName() + "@" + vo.getCustomResCode());
                vo.setExplain(explain);
                String areaName = areaMap.get(vo.getRegionalCode());
                vo.setAreaName((areaName == null || "null".equals(areaName)) ? "" : areaName);

                // 计算客户端传输耗时
                vo.setNetworkTranslateTime(((vo.getClientTranslateTime() == null ? 0 : vo.getClientTranslateTime())
                        + (vo.getServerTranslateTime() == null ? 0 : vo.getServerTranslateTime())) + "");
            }
        }
    }

    /**
     * 获取告警事件档案各个告警周期
     * @param params 请求参数
     * @return 结果集
     */
    public List<String> getGroupCycleTimeList(AlarmDetailParams params) {
        List<String> groupCycleTimeList = new ArrayList<>();
        if (StringUtils.isNotEmpty(params.getGroupId())) {
            // 根据groupId查询对应告警数据的时间周期
            groupCycleTimeList = alarmDetailAnalysisMapper.getGroupCycleTimeList(params);
        } else {
            // 单个周期的告警分析用以下条件
            groupCycleTimeList = Collections.singletonList(params.getStartTime());
        }
        return groupCycleTimeList;
    }

    /**
     * 根据不同的数据类型获取详单ID列表
     * @param params 请求参数
     * @return 结果集
     */
    public List<String> getDetailIdList(AlarmDetailParams params) {
        List<String> detailIdList = new ArrayList<>();
        if (DataTypeUtil.isInterface(params.getDataType())
                && (!CollectionUtils.isEmpty(params.getStepCodes()) || StringUtils.isNotEmpty(params.getStepCode()))) {
            /*
              如果是'接口'类型则需要先获取对应业务，再获取该业务下对应时间段的告警详单ID
              查询 dws_web_status_alarm_stat 表在 groupCycleTimeList 内 涉及到了那些业务
            */
            if (!CollectionUtils.isEmpty(params.getStepCodes())) {
                params.setStepCode(params.getStepCodes().get(0));
            }
            List<String> businessCodeList = alarmDetailAnalysisMapper.getBusinessCodeByResourceCode(params);

            // 根据 业务编码 和时间 groupCycleTimeList 查询 dws_business_alarm_detail 获取详单ID
            if (!CollectionUtils.isEmpty(businessCodeList)) {
                params.setBusinessCodeList(businessCodeList);
                detailIdList = alarmDetailAnalysisMapper.getDetailIdList(params);
            }

        } else if (DataTypeUtil.isPublicStep(params.getDataType())
                && !StringUtils.isEmpty(params.getStepCode())) {
            /*
              如果是'共用步骤'类型则根据步骤编码对应时间段的告警详单ID
            */
            detailIdList = alarmDetailAnalysisMapper.getDetailIdFromStepTable(params);

        } else {

            /*
              如果是‘业务维度’则根据业务编码通过业务详单表(dws_business_alarm_detail)获取详单ID
            */
            detailIdList = alarmDetailAnalysisMapper.getDetailIdFromAlarmDetail(params);
        }

        return detailIdList;
    }

    private Map<String, String> getAreaName() {
        Map<String, String> map = new HashMap<>();
		R<List<Map<String, String>>> result = null;

		try {
			long sTime = System.currentTimeMillis();
			result = remoteAreaService.getAreaCodeAndName();
			log.info("[运维工作台]-[远程调用-后台配置服务]-[获取区域数据]-耗时: {} MS", System.currentTimeMillis() - sTime);
			if (result != null) {
				result.getData().forEach(area -> {
					map.put(area.get("code"), area.get("name"));
				});
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("[运维工作台]-[远程调用-后台配置服务]-[获取区域数据]报错：" + e.getMessage());
		}

		return map;
    }

    /**
     * 手动对数据进行分页处理
     * @param params 请求参数
     * @param resultList 分页目标
     * @return 结果集
     */
    public List<StepAlarmListVo> getAlarmDetailResultListByPage(AlarmDetailParams params, List<StepAlarmListVo> resultList) {
        long pageSize = params.getPageSize(); // 每页条数
        long pageNum = params.getPageNum(); // 页码
        long pageStart = (pageNum - 1) * pageSize;
        long pageEnd = pageStart + pageSize;
        List<StepAlarmListVo> result = new ArrayList<>();
        if (CollectionUtils.isEmpty(resultList)) { return result; }

        long detailIdListSize = resultList.size();
        if (detailIdListSize < pageEnd) { pageEnd = detailIdListSize; }

        for (long i = pageStart; i < pageEnd; i++) {
            result.add(resultList.get((int) i));
        }
        return result;
    }


    /**
     * 手动对数据进行分页处理
     * @param params 请求参数
     * @param resultList 分页目标
     * @return 结果集
     */
    public List<AffectUserVo> getResultListByPage(AlarmDetailParams params, List<AffectUserVo> resultList) {
        long pageSize = params.getPageSize(); // 每页条数
        long pageNum = params.getPageNum(); // 页码
        long pageStart = (pageNum - 1) * pageSize;
        long pageEnd = pageStart + pageSize;
        List<AffectUserVo> result = new ArrayList<>();
        if (CollectionUtils.isEmpty(resultList)) { return result; }

        long detailIdListSize = resultList.size();
        if (detailIdListSize < pageEnd) { pageEnd = detailIdListSize; }

        for (long i = pageStart; i < pageEnd; i++) {
            result.add(resultList.get((int) i));
        }
        return result;
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-影响用户统计-下载
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public void getAffectUserExcel(AlarmDetailParams params, HttpServletResponse response) {
        try {

            //前端未传cycleTime,但startTime和cycleTime同值
            params.setCycleTime(params.getStartTime());
            params.setPageNum(1);
            params.setPageSize(-1);
            List<AffectUserVo> list = BeanUtils.cast(getAffectUser(params).getData());
            for (AffectUserVo affectUserVo : list) {
                String explain =  StringUtils.isEmpty(affectUserVo.getExplain()) ? "" : (" (" + affectUserVo.getExplain() + ")");
                affectUserVo.setCustomResCode(affectUserVo.getCustomResCode() + explain);
                if (affectUserVo.getDealTime() != null && !affectUserVo.getDealTime().contains("ms")) {
                    affectUserVo.setDealTime(getDouble(affectUserVo.getDealTime()) + "ms");
                }
                if (affectUserVo.getServerResponseTime() != null && !affectUserVo.getServerResponseTime().contains("ms")) {
                    affectUserVo.setServerResponseTime(getDouble(affectUserVo.getServerResponseTime()) + "ms");
                }

                if (affectUserVo.getNetworkTranslateTime() != null && !affectUserVo.getNetworkTranslateTime().contains("ms")) {
                    affectUserVo.setNetworkTranslateTime(getDouble(affectUserVo.getNetworkTranslateTime()) + "ms");
                }

                affectUserVo.setDealState("0".equals(affectUserVo.getDealState()) ? "失败" : "成功");
            }
            // 2、导出Excel

            //设置表头
            List<String> headParamsList = new ArrayList<>();
            String systemName = getSystemName(params.getSystemName());
            String businessName = getBusinessName(params.getBusinessName());
            String alarmIndicatorName = getAlarmIndicatorNameByCode(params.getAlarmIndicatorCode());
            String title = "影响用户数据总览表";
            headParamsList.add(title);
            headParamsList.add(systemName);
            headParamsList.add(businessName);
            headParamsList.add(alarmIndicatorName + "分析");

            String fileName = title + System.currentTimeMillis();
            String formTitleName = ExcelTitleUtils.formTitleName(headParamsList, params.getStartTime());
            List<List<String>> headList = new ArrayList<>();
            List<String> titleList = Arrays.asList( "用户名", "系统名称", "业务名称", "开始时间", "结束时间", "响应状态", "业务返回码", "响应耗时", "传输耗时", "办理时长", "归属区域");
            titleList.forEach(s -> {
                List<String> childHeadList = new ArrayList<>();
                childHeadList.add(formTitleName);
                childHeadList.add(s);
                headList.add(childHeadList);
            });

            ExcelUtil.writeExcel(AffectUserVo.class, fileName, title, list, headList, response);
        } catch (Exception e) {
            e.printStackTrace();
		}
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-影响系统统计
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public PageDataInfo getAffectSystem(AlarmDetailParams params) {
        Page<AffectSystemVo> page = new Page<>(params.getPageNum(), params.getPageSize());
        Map<Integer, Integer> systemTypeMap = new HashMap<>();
        Map<String, String> systemTypeDictMap = new HashMap<>();
        List<String> systemIdList = new ArrayList<>();


        if (DataTypeUtil.isInterface(params.getDataType())
                && !CollectionUtils.isEmpty(params.getStepCodes())) {
            // 如果是接口则需要查询web表获取系统, 这里因为整个stepCodes都是一样的所以只需取一个即可
            params.setStepCode(params.getStepCodes().get(0));
            systemIdList = alarmDetailAnalysisMapper.findSystemIdList(params);

        } else {
            // 根据步骤编号，查询系统id
			try {
				long sTime = System.currentTimeMillis();
				systemIdList =  remoteStepService.querySystemIdByStepCode(params.getStepCodes()).getData();
				log.info("[运维工作台]-[远程调用-后台配置服务]-[根据步骤编号，查询系统id]-耗时: {} MS", System.currentTimeMillis() - sTime);
			} catch (Exception e) {
				e.printStackTrace();
				log.error("[运维工作台]-[远程调用-后台配置服务]-[根据步骤编号，查询系统id]报错: {}" + e.getMessage());
			}
        }

        if (CollectionUtils.isEmpty(systemIdList)) { return new PageDataInfo(0, new ArrayList<>()); }

        // 根据 系统id、开始时间、结束时间 查询最终影响系统数据
        params.setSystemIdList(systemIdList);
        List<AffectSystemVo> list = alarmDetailAnalysisMapper.getAffectSystem(page, params);

        // 根据系统ID查询配置表对应系统类型
        /*List<SystemManagementVo> systemManagementVoList = alarmDetailAnalysisMapper.querySystemManagementByIds(systemIdList);
        if (!CollectionUtils.isEmpty(systemManagementVoList)) {
            systemTypeMap = systemManagementVoList.stream().collect(
                    Collectors.toMap(SystemManagementVo::getId, SystemManagementVo::getType, (key1, key2) -> key2)); TODO 远程调用
        }*/

        // 结合字典表查询系统所属类型
		R<List<SysDictItem>> result = null;
		/*try {
			long sTime = System.currentTimeMillis();
			result = remoteDictService.getDictByType(DictType.SYSTEM_TYPE);
			log.info("[运维工作台]-[远程调用-后台配置服务]-[获取字典数据]-耗时: {} MS", System.currentTimeMillis() - sTime);
		} catch (Exception e) {
			e.printStackTrace();
			log.error("[运维工作台]-[远程调用-后台配置服务]-[获取字典数据]报错: {}" + e.getMessage()); TODO 远程调用
		}*/

		if (!Objects.isNull(result)) {
			List<SysDictItem> systemTypeDictList = result.getData();
			if (!CollectionUtils.isEmpty(systemTypeDictList)) {
				systemTypeDictMap = systemTypeDictList.stream().collect(
						Collectors.toMap(SysDictItem::getItemValue, SysDictItem::getLabel, (key1, key2) -> key2));
			}
		}

        // 回填系统所诉类型（'营业厅'、'线上系统'。。。）
        for (AffectSystemVo affectSystemVo : list) {
            Integer type = systemTypeMap.get(affectSystemVo.getSystemId());
            affectSystemVo.setTypeStr(systemTypeDictMap.get(String.valueOf(type)));
        }

        return new PageDataInfo(page, list);
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-影响系统统计-下载
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public void getAffectSystemExcel(AlarmDetailParams params, HttpServletResponse response) throws IOException {
        // 1. 查询要导出的数据
        params.setPageNum(1);
        //由于mybatis-plus 分页查询限制 500 条,小于0 如 -1 不受限制
        params.setPageSize(-1);
        List<AffectSystemVo> list = BeanUtils.cast(getAffectSystem(params).getData());

        for (AffectSystemVo affectSystemVo : list) {
            affectSystemVo.setHealthRate(affectSystemVo.getHealthRate() == null ? "" : (affectSystemVo.getHealthRate() + "%"));
        }

        // 2.处理标题
        List<String> headParamsList = new ArrayList<>();
        String systemName = getSystemName(params.getSystemName());
        String businessName = getBusinessName(params.getBusinessName());
        String alarmIndicatorName = getAlarmIndicatorNameByCode(params.getAlarmIndicatorCode());
        String title = "影响系统数据总览表";
        headParamsList.add(title);
        headParamsList.add(systemName);
        headParamsList.add(businessName);
        headParamsList.add(alarmIndicatorName + "关联分析");
        String fileName = title + System.currentTimeMillis();
        String formTitleName = ExcelTitleUtils.formTitleName(headParamsList, params.getStartTime());
        List<List<String>> headList = new ArrayList<>();
        List<String> titleList = Arrays.asList("系统名称", "系统分类", "请求量", "告警请求量", "失败请求量", "健康度");
        titleList.forEach(s -> {
            List<String> childHeadList = new ArrayList<>();
            childHeadList.add(formTitleName);
            childHeadList.add(s);
            headList.add(childHeadList);
        });

        // 3.导出数据
        ExcelUtil.writeExcel(AffectSystemVo.class, fileName, title, list, headList, response);
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-影响业务统计
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public PageDataInfo getAffectBusiness(AlarmDetailParams params) {
        Page<AffectBusinessVo> page = new Page<>(params.getPageNum(), params.getPageSize());
        List<String> businessCodeList = new ArrayList<>();

        if (DataTypeUtil.isInterface(params.getDataType())
                && !CollectionUtils.isEmpty(params.getStepCodes())) {
            // 如果是接口则需要查询web表获取业务, 这里因为整个stepCodes都是一样的所以只需取一个即可
            params.setStepCode(params.getStepCodes().get(0));
            businessCodeList = alarmDetailAnalysisMapper.findBusinessCodeListByWeb(params);

        } else {
            // 如果是步骤则根据步骤编号，查询业务编码列表
			try {
				long sTime = System.currentTimeMillis();
				businessCodeList =  remoteBusinessService.queryBusinessCodeListByStepCode(params.getStepCodes()).getData();
				log.info("[运维工作台]-[远程调用-后台配置服务]-[根据步骤编号，查询所属业务]-耗时: {} MS", System.currentTimeMillis() - sTime);
			} catch (Exception e) {
				e.printStackTrace();
				log.error("[运维工作台]-[远程调用-后台配置服务]-[根据步骤编号，查询所属业务]报错: " + e.getMessage());
			}
        }

        if (CollectionUtils.isEmpty(businessCodeList)) { return new PageDataInfo(0, new ArrayList<>()); }

        // 根据业务编码列表和开始、结束时间查询对应业务数据
        params.setBusinessCodeList(businessCodeList);
        List<AffectBusinessVo> list = alarmDetailAnalysisMapper.getAffectBusiness(page, params);

        return new PageDataInfo(page, list);
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-影响业务统计-下载
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public void getAffectBusinessExcel(AlarmDetailParams params, HttpServletResponse response) {
        try {
            // 1、查询要导出的数据
            params.setPageNum(1);
            params.setPageSize(-1);
            List<AffectBusinessVo> list = BeanUtils.cast(getAffectBusiness(params).getData());

            for (AffectBusinessVo s : list) { s.setHealthRate(s.getHealthRate() + "%"); }

            // 2、导出Excel

            // 设置表头
            List<String> headParamsList = new ArrayList<>();
            String systemName = getSystemName(params.getSystemName());
            String businessName = getBusinessName(params.getBusinessName());
            String alarmIndicatorName = getAlarmIndicatorNameByCode(params.getAlarmIndicatorCode());
            String title = "影响业务数据总览表";

            headParamsList.add(title);
            headParamsList.add(systemName);
            headParamsList.add(businessName);
            headParamsList.add(alarmIndicatorName + "关联分析");

            String fileName = title + System.currentTimeMillis();
            String formTitleName = ExcelTitleUtils.formTitleName(headParamsList, params.getStartTime());
            List<List<String>> headList = new ArrayList<>();
            List<String> titleList = Arrays.asList("业务名称", "系统名称", "访问量", "响应超时访问量", "告警访问量", "失败访问量", "健康度");
            titleList.forEach(s -> {
                List<String> childHeadList = new ArrayList<>();
                childHeadList.add(formTitleName);
                childHeadList.add(s);
                headList.add(childHeadList);
            });

            ExcelUtil.writeExcel(AffectBusinessVo.class, fileName, title, list, headList, response);
        } catch (Exception e) {
            e.printStackTrace();

		}
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-区域告警统计分析
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public List<BusiAreaAlarmStatVo>  getAreaAlarmAnalysis(AlarmDetailParams params) {
        List<String> groupCycleTimeList = new ArrayList<>();
        if (params.getGroupId() != null) {
            // 根据groupId查询对应告警数据的时间周期
            groupCycleTimeList = alarmDetailAnalysisMapper.getGroupCycleTimeList(params);
        } else {
            groupCycleTimeList = Collections.singletonList(params.getStartTime());
        }
        params.setGroupCycleTimeList(groupCycleTimeList);
        List<String> businessCodeList = new ArrayList<>();
        if (DataTypeUtil.isPublicStep(params.getDataType())) {
            // 如果是步骤则根据步骤编号，查询业务编码列表
            businessCodeList = alarmDetailAnalysisMapper.findBusinessCodeListByStepCode2(params);
            if (businessCodeList.size() == 0) { return null; }
            params.setBusinessCodeList(businessCodeList);

        } else if (DataTypeUtil.isInterface(params.getDataType())) {
            // 如果是接口则根据业务服务资源编码，查询业务编码列表
            businessCodeList = alarmDetailAnalysisMapper.getBusinessCodeByResourceCode(params);
			if (businessCodeList.size() == 0) {
				List<BusiAreaAlarmStatVo> result = new ArrayList<>();
				result.add(new BusiAreaAlarmStatVo());
				return result;
			}
            params.setBusinessCodeList(businessCodeList);
        }

        // 根据业务编码和时间列表查询对应数据
        return alarmDetailAnalysisMapper.getAreaAlarmAnalysis(params);
    }

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-共用步骤分析
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public PageDataInfo getPublicStepAnalysis(AlarmDetailParams params) {
		List<PublicStepAnalysisVo> resultList = new ArrayList<>();
		Page<PublicStepAnalysisVo> page = new Page<>(params.getPageNum(), params.getPageSize());
		// 根据groupId查询对应告警数据的时间周期
		List<String> groupCycleTimeList = new ArrayList<>();
		if (params.getGroupId() != null) {
			groupCycleTimeList = alarmDetailAnalysisMapper.getGroupCycleTimeList(params);
		} else {
			groupCycleTimeList.add(params.getCycleTime());
		}

		// 按批次滚动查询数据避免一次性查导致超时问题
		List<List<String>> cycleTimeList = Lists.partition(groupCycleTimeList, QUERY_BATCH_SIZE);

		long startTime = System.currentTimeMillis();
		String taskName = "TASK-STEP-ANALYSIS";
		try {
			// Create a thread pool to execute the queries in parallel
			ExecutorService executor = TaskProcessUtil.getOrInitExecutors(taskName, THREAD_POOL_SIZE);
			final CountDownLatch latch = new CountDownLatch(cycleTimeList.size());
			List<Future<List<PublicStepAnalysisVo>>> futures = new ArrayList<>();
			for (List<String> cycleTimeChildList : cycleTimeList) {
				Callable<List<PublicStepAnalysisVo>> task = new SqlInQueryTask2(params, latch, cycleTimeChildList);
				Future<List<PublicStepAnalysisVo>> future = executor.submit(task);
				futures.add(future);
			}

			for (Future<List<PublicStepAnalysisVo>> future : futures) { resultList.addAll(future.get()); }
			log.info("运维工作台-告警分析-接口分析查询数据总耗时：{} ms", System.currentTimeMillis() - startTime);

		} catch (Exception e) {
			log.error("运维工作台-告警分析-接口分析查询报错", e);
		} finally {
			// Shut down the thread pool
			TaskProcessUtil.releaseExecutors(taskName);
		}


		int index = 1;
		for (PublicStepAnalysisVo vo : resultList) {
			vo.setIndex(index++);
			vo.setRequestIpPort(vo.getRequestIp() + ":" + vo.getRequestPort());
		}

		// 6.如果是列表查询则需要手动分页返回到前端，下载则不用分页
		if (params.getPageSize() != -1) {
			page.setTotal(resultList.size());
			resultList = getResultListByPage2(params, resultList);
		}

		return new PageDataInfo(page, resultList);
    }



	private class SqlInQueryTask2 implements Callable<List<PublicStepAnalysisVo>> {

		private final AlarmDetailParams params;

		private final CountDownLatch latch;

		private final List<String> cycleTimeChildList;

		public SqlInQueryTask2(AlarmDetailParams params, CountDownLatch latch, List<String> cycleTimeChildList) {
			this.params = params;
			this.latch = latch;
			this.cycleTimeChildList = cycleTimeChildList;
		}

		@Override
		public List<PublicStepAnalysisVo> call() {
			List<PublicStepAnalysisVo> list = new ArrayList<>();
			try {
				if (DataTypeUtil.isInterface(params.getDataType())) {
					// 共用接口需要查询 web 表
					list = alarmDetailAnalysisMapper.getPublicWebAnalysis(params, cycleTimeChildList);
				} else {
					// 共用步骤需要查询 step 表
					list = alarmDetailAnalysisMapper.getPublicStepAnalysis(params, cycleTimeChildList);
				}

			} catch (Exception e) {
				log.error("运维工作台-告警分析-影响用户查询报错", e);

			} finally {
				if (latch != null) { latch.countDown();  }
			}
			return list;
		}
	}

	/**
	 * 手动对数据进行分页处理
	 * @param params 请求参数
	 * @param resultList 分页目标
	 * @return 分页结果
	 */
	public List<PublicStepAnalysisVo> getResultListByPage2(AlarmDetailParams params, List<PublicStepAnalysisVo> resultList) {
		long pageSize = params.getPageSize(); // 每页条数
		long pageNum = params.getPageNum(); // 页码
		long pageStart = (pageNum - 1) * pageSize;
		long pageEnd = pageStart + pageSize;
		List<PublicStepAnalysisVo> result = new ArrayList<>();
		if (CollectionUtils.isEmpty(resultList)) { return result; }

		long detailIdListSize = resultList.size();
		if (detailIdListSize < pageEnd) { pageEnd = detailIdListSize; }

		for (long i = pageStart; i < pageEnd; i++) {
			result.add(resultList.get((int) i));
		}
		return result;
	}

    /**
     * BPM 4.0
     * 运维工作台-告警详情分析-共用步骤接口分析-下载
     * @param params 请求参数
     * @return 结果集
     */
    @Override
    public void getPublicStepAnalysisExcel(AlarmDetailParams params, HttpServletResponse response) {
        try {
            // 1、查询要导出的数据
            params.setPageNum(1);
            params.setPageSize(-1);
            List<PublicStepAnalysisVo> list = BeanUtils.cast(getPublicStepAnalysis(params).getData());

            // 2、导出Excel
			// 2、导出Excel
			String title = "接口分析数据总览表" +
					(params.getDataType() == 3 ? "-共用步骤" : "-共用接口") + "-" +
					params.getStepName() +  "-" +
					("BS_FAIL_RATE_10".equals(params.getAlarmIndicatorCode())  ? "-失败率" : "-响应慢") + "告警分析" +
					" " + params.getStartTime() ;

			List<List<String>> headList = new ArrayList<>();
            List<String> titleList = Arrays.asList("序号", "请求源", "调用量", "最早调用时间", "调用时间");
            titleList.forEach(s -> {
                List<String> childHeadList = new ArrayList<>();
                childHeadList.add(title);
                childHeadList.add(s);
                headList.add(childHeadList);
            });

            ExcelUtil.writeExcel(PublicStepAnalysisVo.class, title, title, list, headList, response);
        } catch (Exception e) {
            e.printStackTrace();

		}
    }

    public Map<String, String> getCodeExplain() {
        Map<String, String> resMap = new HashMap<>();
		ConfReturnCode params = new ConfReturnCode();
		// 4:自定返回码类型  3: 数据库返回码 2：中间件返回码  1：web 返回码
		params.setTypeId(4);  // TODO 配置库数据迁移完成后需要确认此处的类型ID是否和原来的保持一致
		try {
			long sTime = System.currentTimeMillis();
			R<List<ConfReturnCode>> result = remoteReturnCodeService.getCodeDetail(params);
			log.info("[运维工作台]-[远程调用-后台配置服务]-[获取自定义返回码信息]-耗时: {} MS", System.currentTimeMillis() - sTime);
			if (result != null) {
				result.getData()
						.forEach(returnCode -> {
							resMap.put(returnCode.getName() + "@" + returnCode.getValue(), returnCode.getExplain());
						});
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("[运维工作台]-[远程调用-后台配置服务]-[获取自定义返回码信息]报错: {}" + e.getMessage());
		}
        return resMap;
    }

	public static String getDouble(String str) {
        double time = Double.parseDouble(str);
        DecimalFormat df = new DecimalFormat("#########0.00");
        return df.format(time/1000);
    }


    public static String getAlarmTypeName(List<Integer> alarmTypeId) {
        StringBuilder alarmType = new StringBuilder();
        if (alarmTypeId == null || alarmTypeId.size() == 0 || alarmTypeId.size() == 4) {
            alarmType = new StringBuilder("所有");
            return alarmType.toString();
        }
        for (Integer s : alarmTypeId) {
            if (1 == s) {
                alarmType.append("处理时长、");
            }
            if (2 == s) {
                alarmType.append("服务器响应时长、");
            }
            if (3 == s) {
                alarmType.append("客户端网络传输时长、");
            }
            if (4 == s) {
                alarmType.append("服务器网络传输时长、");
            }
            if (5 == s) {
                alarmType.append("服务器返回码、");
            }
            if (6 == s) {
                alarmType.append("自定义返回码、");
            }
            if (7 == s) {
                alarmType.append("提示语、");
            }
            if (8 == s) {
                alarmType.append("数据完整状态、");
            }
        }
        alarmType = new StringBuilder(alarmType.substring(0, alarmType.length() - 1));
        return alarmType.toString();
    }

    /**
     * list手动分页
     * @return 结果集
     */
    private List<AlarmClassifyVo> getListPage(int page,int pageSize,List<AlarmClassifyVo> list){
        if (pageSize == -1) { return list; }
        if (list == null || list.size() == 0) {
            throw new RuntimeException("分页数据不能为空");
        }
        int totalCount = list.size();
        page = page -1;
        int fromIndex = page * pageSize;
		// 分页不能大于总数
		if (fromIndex >= totalCount){
			fromIndex = 0;
			log.info("运维工作台-告警详情-告警分类统计: 手动分页，重置前端页码，原页码：{}，每页条数：{}， 数据总数：{}", page+1, pageSize, totalCount);
		}
        int toIndex = ((page+1) * pageSize);
        if (toIndex > totalCount) {
            toIndex = totalCount;
        }
        return list.subList(fromIndex, toIndex);
    }

    /**
     * 更具步骤编码列表获取步骤类型
     * @param stepCodeList
     */
    private Map<String, String> getStepTypeByStepCode(List<String> stepCodeList) {
        Map<String, String> stepTypeMap = new HashMap<>();
        // 查询步骤类型列表
		try {
			long sTime = System.currentTimeMillis();
			stepTypeMap = remoteStepService.queryStepTypeByBusinessCode().getData();
			log.info("[运维工作台]-[远程调用-后台配置服务]-[获取步骤类型]-耗时: {} MS", System.currentTimeMillis() - sTime);
		} catch (Exception e) {
			e.printStackTrace();
			log.error("[运维工作台]-[远程调用-后台配置服务]-[获取步骤类型]报错: {}" + e.getMessage());
		}

        return stepTypeMap;
    }

    private void getHeadList(List<String> titleList, List<List<String>>  headList, String formTitleName) {
        titleList.forEach(s -> {
            List<String> childHeadList = new ArrayList<>();
            childHeadList.add(formTitleName);
            childHeadList.add(s);
            headList.add(childHeadList);
        });

    }

    /**
     * 格式化系统名称
     * @return 结果集
     */
    public static String getSystemName(String systemName) {
        return systemName == null ? "未知系统" : (systemName + "系统");
    }

    /**
     * 格式化业务名称
     * @return 结果集
     */
    public static String getBusinessName(String businessName) {
        return businessName == null ? "未知业务" : (businessName + "业务");
    }

    /**
     * 格式化告警指标名称
     * @return 结果集
     */
    public static String getAlarmIndicatorName(String alarmIndicatorName) {
        return alarmIndicatorName == null ? "未知告警指标" : (alarmIndicatorName);
    }

    /**
     * 格式化告警指标名称
     * @return 结果集
     */
    public static String getAlarmIndicatorNameByCode(String alarmIndicatorCode) {
        String alarmIndicatorName = "";

        switch (alarmIndicatorCode) {
            case "BS_FAIL_RATE_10":
                alarmIndicatorName = "失败率高告警";
                break;
            case "BS_FAIL_RATE_20":
                alarmIndicatorName = "响应慢告警";
                break;
			case "BS_FAIL_RATE_30":
				alarmIndicatorName = "健康度低告警";
				break;
            default:
                alarmIndicatorName = "未知告警指标";
        }

        return alarmIndicatorName;
    }
}
